package main

import (
	"bufio"
	"bytes"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"gitee-lfs-multipart-transfer/internal/common"
	"io"
	"net/http"
	"os"
	"strconv"
)

type MultipartUploader struct {
	r *bufio.Reader
}

func NewMultipartUploader() *MultipartUploader {
	return &MultipartUploader{
		r: bufio.NewReader(os.Stdin),
	}
}

func (m *MultipartUploader) Exec() error {
	if initErr := m.handleInitEvent(); initErr != nil {
		m.sendEvent(&common.Event{
			Error: &common.Error{
				Code:    common.ERROR_CODE_INIT_FAILED,
				Message: initErr.Error(),
			},
		})
		return initErr
	}

	m.sendEventInitSuccess()

	for {
		event, err := m.decodeEvent()
		if err != nil {
			return err
		}

		// skip empty line
		if event == nil {
			continue
		}

		if event.Event == common.EventTerminate {
			break
		}

		er := &common.Event{
			Event: common.EventComplete,
			Oid:   event.Oid,
		}

		if err := m.handleEvent(event); err != nil {
			er.Error = &common.Error{
				Code:    common.ERROR_CODE_UPLOAD_FAILED,
				Message: err.Error(),
			}
		}
		m.sendEvent(er)
	}

	return nil
}

func (m *MultipartUploader) handleEvent(event *common.Event) error {
	if event.Action.Header == nil {
		return m.sendFileSingle(event)
	}

	if event.Action.Header[common.TRANSFER] == common.TRANSFER_MULTIPART {
		return m.sendFileMultiparts(event)
	}

	return m.sendFileSingle(event)
}

func (m *MultipartUploader) sendFileSingle(event *common.Event) error {
	f, err := os.Open(event.FilePath)
	if err != nil {
		return fmt.Errorf("open file error: %v", err)
	}
	defer f.Close()

	r := common.NewCountingReader(f, func(totalReadLen, readLen int64) {
		processEvent := &common.Event{
			Event:          common.EventProgress,
			Oid:            event.Oid,
			BytesSoFar:     totalReadLen,
			BytesSinceLast: readLen,
		}

		m.sendEvent(processEvent)
	})

	reqSend, err := common.NewRequest("PUT", event.Action.Href, r, event.Action.Header)
	if err != nil {
		return fmt.Errorf("create upload file request error: %v", err)
	}

	reqSend.ContentLength = event.Size

	resp, err := http.DefaultClient.Do(reqSend)
	if err != nil {
		return fmt.Errorf("send upload file request error: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode < 200 || resp.StatusCode > 299 {
		return fmt.Errorf("upload file by single response %d", resp.StatusCode)
	}

	return nil
}

func (m *MultipartUploader) sendFileMultiparts(event *common.Event) error {
	f, err := os.Open(event.FilePath)
	if err != nil {
		return fmt.Errorf("open file error: %v", err)
	}
	defer f.Close()

	obj := &common.LfsObject{
		Oid:  event.Oid,
		Size: event.Size,
	}

	href, err := base64.StdEncoding.DecodeString(event.Action.Href)
	if err != nil {
		return fmt.Errorf("decode base64 href error: %v", err)
	}

	var parts []*common.Link
	if err := json.Unmarshal(href, &parts); err != nil {
		return fmt.Errorf("unmarshal parts error: %v", err)
	}

	alreadyUploadBytes := int64(0)
	if uploadedSizeStr, ok := event.Action.Header[common.HEADER_ALREADY_UPLOADED_SIZE]; ok {
		if uploadedSize, err := strconv.Atoi(uploadedSizeStr); err == nil {
			alreadyUploadBytes = int64(uploadedSize)

			processEvent := &common.Event{
				Event:          common.EventProgress,
				Oid:            obj.Oid,
				BytesSinceLast: alreadyUploadBytes,
				BytesSoFar:     alreadyUploadBytes,
			}
			m.sendEvent(processEvent)
		}
	}

	for _, part := range parts {
		if err := m.sendFileMultipart(part, obj, f, alreadyUploadBytes); err != nil {
			return err
		}

		alreadyUploadBytes += part.PartSize
	}

	return nil
}

func (m *MultipartUploader) sendFileMultipart(part *common.Link, obj *common.LfsObject, body io.ReaderAt, alreadyUploadBytes int64) error {
	r := common.NewCountingReader(io.NewSectionReader(body, part.PartOffset, part.PartSize), func(totalReadLen, readLen int64) {
		processEvent := &common.Event{
			Event:          common.EventProgress,
			Oid:            obj.Oid,
			BytesSinceLast: readLen,
			BytesSoFar:     totalReadLen + alreadyUploadBytes,
		}

		m.sendEvent(processEvent)
	})

	reqSend, err := common.NewRequest("PUT", part.Href, r, part.Header)
	if err != nil {
		return fmt.Errorf("create upload part request error: %v", err)
	}

	// upload to oss must set content length
	reqSend.ContentLength = part.PartSize

	resp, err := http.DefaultClient.Do(reqSend)
	if err != nil {
		return fmt.Errorf("send upload part request error: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode < 200 || resp.StatusCode > 299 {
		return fmt.Errorf("upload file for muitlpart response %d", resp.StatusCode)
	}

	etag := resp.Header.Get("Etag")
	if len(etag) == 0 {
		return errors.New("missing etag after upload chunk")
	}

	vc := &common.VerifyChunk{
		Object: obj,
		CompletedPart: &common.CompletedPart{
			PartNumber: part.PartNumber,
			ETag:       etag,
		},
	}

	var buf bytes.Buffer
	if err := json.NewEncoder(&buf).Encode(vc); err != nil {
		return fmt.Errorf("encode chunk verify request body error: %v", err)
	}

	reqVerifyChunk, err := common.NewRequest("POST", part.ChunkVerify.Href, &buf, part.ChunkVerify.Header)
	if err != nil {
		return fmt.Errorf("create verify chunk request error: %v", err)
	}

	respChunk, err := http.DefaultClient.Do(reqVerifyChunk)
	if err != nil {
		return fmt.Errorf("send verify part request error: %v", err)
	}
	defer respChunk.Body.Close()

	if respChunk.StatusCode < 200 || respChunk.StatusCode > 299 {
		return fmt.Errorf("upload file for verify chunk response %d", respChunk.StatusCode)
	}

	return nil
}

func (m *MultipartUploader) handleInitEvent() error {
	eventInit, err := m.decodeEvent()
	if err != nil {
		return err
	}

	if eventInit.Event != common.EventInit {
		return errors.New("event type not implemented")
	}

	if eventInit.Operation != common.OPT_UPLOAD {
		return errors.New("operation not implemented")
	}

	return nil
}

func (m *MultipartUploader) sendEventInitSuccess() {
	fmt.Println("{ }")
}

func (m *MultipartUploader) sendEvent(event *common.Event) error {
	encoder := json.NewEncoder(os.Stdout)
	return encoder.Encode(event)
}

func (m *MultipartUploader) decodeEvent() (*common.Event, error) {
	b, err := m.r.ReadBytes('\n')
	if err != nil {
		return nil, err
	}

	var event common.Event
	if err := json.Unmarshal(b, &event); err != nil {
		return nil, err
	}

	return &event, nil
}

func CmdMultipartuploader() error {
	return NewMultipartUploader().Exec()
}
